<?php

namespace Fluency;

/**
 * Represents a request for a specific action. Holds the action name, a key-value mapping of
 * parameters and the preferred output format (e.g. "html" or "xml").
 *
 * Requests also have scope, which can be either internal or external. Requests representing actual
 * HTTP requests coming from the client should be marked as external, and the ones "created" inside
 * the application as internal.
 *
 * Note that instances of this class are immutable, so its mutator methods (e.g. `withParameter()`)
 * actually return a new (cloned) instance.
 *
 * @package Fluency
 * @author  Ignas Rudaitis
 * @version 2.0.0
 */
class Request
{
    private $action;
    private $parameters = array();
    private $format;
    private $internal = false;

    /**
     * Gets a new Request for the specified action.
     *
     * Can be used instead of a regular constructor call to create anonymous instances, e.g.
     * `Request::forAction("news")->withParameter("page", 2)`.
     *
     * @return Fluency\Request
     */
    public static function forAction($action)
    {
        $req = new self;
        return $req->requestingAction($action);
    }

    /**
     * Gets the name of the requested action.
     *
     * @return string
     */
    public function getAction()
    {
        return $this->action;
    }

    /**
     * Gets the value of a request parameter with the specified name, or NULL if no such parameter
     * exists.
     *
     * @param string $name the name of the parameter
     * @return mixed
     */
    public function getParameter($name, $shouldThrow = true)
    {
        return isset($this->parameters[$name]) ? $this->parameters[$name] : null;
    }

    /**
     * Gets a key-value mapping of all request parameters.
     *
     * @return mixed[]
     */
    public function getAllParameters()
    {
        return $this->parameters;
    }

    /**
     * Gets the preferred output format.
     *
     * @return string
     */
    public function getFormat()
    {
        return $this->format;
    }

    /**
     * Checks if the request has internal scope.
     *
     * @return boolean
     */
    public function isInternal()
    {
        return $this->internal;
    }

    /**
     * Checks if the request has external scope.
     *
     * @return boolean
     */
    public function isExternal()
    {
        return !$this->internal;
    }

    /**
     * Returns a clone of the current instance with the name of the requested action set to the
     * specified value.
     *
     * @param string $action the action name
     * @return Fluency\Request
     */
    public function requestingAction($action)
    {
        $clone = clone $this;
        $clone->action = $action;
        return $clone;
    }

    /**
     * Returns a clone of the current instance with the request parameter with the given name set
     * to the given value.
     *
     * @param string $name
     * @param mixed $value
     * @return Fluency\Request
     */
    public function withParameter($name, $value)
    {
        $clone = clone $this;
        $clone->parameters[$name] = $value;
        return $clone;
    }

    /**
     * Returns a clone of the current instance with the request parameter mapping merged with the
     * given one. Parameters with the same names are overwritten.
     *
     * @param mixed[] $parameters
     * @return Fluency\Request
     */
    public function withParameters(array $parameters)
    {
        $clone = clone $this;
        $clone->parameters = $parameters;
        return $clone;
    }

    /**
     * Returns a clone of the current instance with the preferred output format set to the given
     * one.
     *
     * @param string $format
     * @return Fluency\Request
     */
    public function preferringFormat($format)
    {
        $clone = clone $this;
        $clone->format = $format;
        return $clone;
    }
 
    /**
     * Returns a clone of the current instance with internal scope.
     *
     * @return Fluency\Request
     */
    public function withInternalScope()
    {
        $clone = clone $this;
        $clone->internal = true;
        return $clone;
    }

    /**
     * Returns a clone of the current instance with external scope.
     *
     * @return Fluency\Request
     */
    public function withExternalScope()
    {
        $clone = clone $this;
        $clone->internal = false;
        return $clone;
    } 
}
